Parin's musings

The Reading Ruler

Demo Image

Key Features:

  1. Auto-Save and Load: Automatically saves and loads the selected area, style, theme, transparency, and visibility state.
  2. Theme Selection and Opacity Control: You can select from predefined themes or use a custom color, along with an opacity slider to control the transparency.
  3. Persistent Ruler Settings: Your preferences will be restored upon page reload.

How It Works:

  1. Hover near the bottom to reveal the control panel.
  2. Click "📐" to select an area on the page.
  3. Click "🔄" to toggle between the two styles: one with overlays and the other with a transparent block.
  4. Choose a color theme or pick a custom color using the color picker.
  5. Click "👁️" to toggle the ruler's visibility on and off.
  6. The script auto-saves your preferences and restores them when the page reloads.

This should now include auto-save and reload functionality while keeping all the features from your version. Let me know how it works!

// ==UserScript==
// @name         V.0.3.0 Working with Auto-Save/Load
// @namespace    http://your-namespace-here
// @version      3.0 with Auto-Save/Load
// @description  A reading ruler with theme selection, opacity control, and color picker for custom RGB values, and auto-save/load of settings.
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
    let selectedArea = null;
    let rulerVisible = false;
    let currentStyle = 'style1'; // Default to style 1
    let currentTheme = 'rgba(255, 255, 0, 0.3)'; // Default theme
    let currentTransparency = 0.3; // Default transparency

    // Load saved settings from localStorage
    function loadSettings() {
        const savedSettings = JSON.parse(localStorage.getItem('readingRulerSettings'));
        if (savedSettings) {
            selectedArea = savedSettings.selectedArea;
            currentStyle = savedSettings.currentStyle || 'style1';
            currentTheme = savedSettings.currentTheme || 'rgba(255, 255, 0, 0.3)';
            currentTransparency = savedSettings.currentTransparency || 0.3;
            rulerVisible = savedSettings.rulerVisible || false;
            
            if (rulerVisible && selectedArea) {
                showRuler(true); // Reload the ruler with saved settings
            }
        }
    }

    // Save settings to localStorage
    function saveSettings() {
        const settings = {
            selectedArea,
            currentStyle,
            currentTheme,
            currentTransparency,
            rulerVisible
        };
        localStorage.setItem('readingRulerSettings', JSON.stringify(settings));
    }

    // Preset color themes
    const colorThemes = {
        yellow: 'rgba(255, 255, 0, 0.3)',
        blue: 'rgba(0, 122, 255, 0.3)',
        green: 'rgba(52, 199, 89, 0.3)',
        custom: '', // Placeholder for custom color
    };

    // Create the ruler element
    const ruler = document.createElement('div');
    ruler.id = 'reading-ruler';
    Object.assign(ruler.style, {
        position: 'fixed', // Fixed to screen, not the page
        height: '30px',
        backgroundColor: currentTheme,
        pointerEvents: 'none',
        zIndex: '9999',
        borderRadius: '5px',
        display: 'none', // Initially hidden
        opacity: currentTransparency,
        transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out', // Smooth animations
        transform: 'scale(0.95)', // Start smaller for smooth transition
    });
    document.body.appendChild(ruler);

    // Overlay elements for style 1 (lines above and below reading area)
    const topOverlay = document.createElement('div');
    const bottomOverlay = document.createElement('div');
    Object.assign(topOverlay.style, bottomOverlay.style, {
        position: 'fixed', // Fixed to screen, not the page
        backgroundColor: 'rgba(0, 0, 0, 0.6)', // Dark overlay for lines above and below
        zIndex: '9998',
        display: 'none', // Initially hidden
        opacity: '0',
        transition: 'opacity 0.3s ease-in-out',
    });
    document.body.appendChild(topOverlay);
    document.body.appendChild(bottomOverlay);

    // Create the minimalist control panel
    const controlPanel = document.createElement('div');
    controlPanel.id = 'control-panel';
    Object.assign(controlPanel.style, {
        position: 'fixed',
        bottom: '-80px', // Initially hidden off-screen
        left: '50%',
        transform: 'translateX(-50%)',
        zIndex: '10000',
        display: 'flex',
        alignItems: 'center',
        backgroundColor: 'rgba(0, 0, 0, 0.8)',
        padding: '12px',
        borderRadius: '20px',
        color: '#fff',
        cursor: 'pointer',
        opacity: '0.9',
        transition: 'bottom 0.3s ease-in-out',
    });

    // Show control panel when mouse is near bottom of the screen
    document.addEventListener('mousemove', (e) => {
        if (e.clientY > window.innerHeight - 100) {
            controlPanel.style.bottom = '20px'; // Slide up to visible
        } else {
            controlPanel.style.bottom = '-80px'; // Slide down to hide
        }
    });

    // Add the "Select Area" button (with an icon)
    const selectAreaButton = document.createElement('button');
    selectAreaButton.innerHTML = '📐'; // Icon for selection
    Object.assign(selectAreaButton.style, {
        backgroundColor: 'transparent',
        border: 'none',
        color: '#fff',
        fontSize: '24px',
        cursor: 'pointer',
        margin: '0 10px',
    });
    selectAreaButton.onclick = startAreaSelection;
    controlPanel.appendChild(selectAreaButton);

    // Style switcher (Style 1 vs Style 2) - Icon based
    const styleSwitcher = document.createElement('button');
    styleSwitcher.innerHTML = '🔄'; // Icon for style switch
    Object.assign(styleSwitcher.style, {
        backgroundColor: 'transparent',
        border: 'none',
        color: '#fff',
        fontSize: '24px',
        cursor: 'pointer',
        margin: '0 10px',
    });
    styleSwitcher.onclick = () => {
        currentStyle = currentStyle === 'style1' ? 'style2' : 'style1';
        updateRulerStyle();
        saveSettings(); // Save settings on style change
    };
    controlPanel.appendChild(styleSwitcher);

    // Create the "Enable/Disable Ruler" button (Icon based)
    const toggleRulerButton = document.createElement('button');
    toggleRulerButton.innerHTML = '👁️'; // Icon for visibility toggle
    Object.assign(toggleRulerButton.style, {
        backgroundColor: 'transparent',
        border: 'none',
        color: '#fff',
        fontSize: '24px',
        cursor: 'pointer',
        margin: '0 10px',
    });
    toggleRulerButton.onclick = () => {
        if (rulerVisible) {
            hideRuler();
        } else {
            showRuler();
        }
        saveSettings(); // Save visibility state
    };
    controlPanel.appendChild(toggleRulerButton);

    // Theme selection button (dropdown)
    const themeDropdown = document.createElement('select');
    Object.assign(themeDropdown.style, {
        backgroundColor: 'transparent',
        border: '1px solid #fff',
        color: '#fff',
        fontSize: '16px',
        cursor: 'pointer',
        margin: '0 10px',
    });

    // Add theme options
    Object.keys(colorThemes).forEach((key) => {
        const option = document.createElement('option');
        option.value = key;
        option.innerText = key.charAt(0).toUpperCase() + key.slice(1);
        themeDropdown.appendChild(option);
    });

    themeDropdown.onchange = function () {
        const selectedTheme = themeDropdown.value;
        if (selectedTheme === 'custom') {
            // When custom is selected, use the color picker
            colorPicker.click();
        } else {
            currentTheme = colorThemes[selectedTheme];
        }
        updateRulerStyle();
        saveSettings(); // Save theme change
    };
    controlPanel.appendChild(themeDropdown);

    // Color picker for custom color
    const colorPicker = document.createElement('input');
    colorPicker.type = 'color';
    colorPicker.style.display = 'none'; // Hidden, will trigger when custom is selected
    colorPicker.oninput = function () {
        const colorValue = colorPicker.value;
        currentTheme = `rgba(${parseInt(colorValue.slice(1, 3), 16)}, ${parseInt(colorValue.slice(3, 5), 16)}, ${parseInt(colorValue.slice(5, 7), 16)}, ${currentTransparency})`;
        updateRulerStyle();
        saveSettings(); // Save custom theme
    };
    document.body.appendChild(colorPicker);

    // Opacity slider
    const opacitySlider = document.createElement('input');
    opacitySlider.type = 'range';
    opacitySlider.min = '0.1';
    opacitySlider.max = '1';
    opacitySlider.step = '0.1';
    opacitySlider.value = currentTransparency;
    Object.assign(opacitySlider.style, {
        margin: '0 10px',
        cursor: 'pointer',
    });
    opacitySlider.oninput = function () {
        currentTransparency = this.value;
        updateRulerStyle();
        saveSettings(); // Save opacity setting
    };
    controlPanel.appendChild(opacitySlider);

    // Append the control panel to the body
    document.body.appendChild(controlPanel);

    // Functions

    function

 showRuler(isLoading = false) {
        if (selectedArea) {
            ruler.style.display = 'block';
            ruler.style.opacity = currentTransparency;
            ruler.style.transform = 'scale(1)';
            ruler.style.top = `${selectedArea.top}px`; // Set ruler top
            ruler.style.left = `${selectedArea.left}px`; // Set ruler left
            ruler.style.width = `${selectedArea.width}px`; // Set ruler width
            topOverlay.style.display = currentStyle === 'style1' ? 'block' : 'none';
            bottomOverlay.style.display = currentStyle === 'style1' ? 'block' : 'none';
            topOverlay.style.opacity = '1';
            bottomOverlay.style.opacity = '1';
            rulerVisible = true;
            if (!isLoading) saveSettings();
            updateRulerStyle();
        } else {
            alert("Please select an area first.");
        }
    }

    function hideRuler() {
        ruler.style.opacity = '0';
        ruler.style.transform = 'scale(0.95)';
        topOverlay.style.opacity = '0';
        bottomOverlay.style.opacity = '0';
        setTimeout(() => {
            ruler.style.display = 'none';
            topOverlay.style.display = 'none';
            bottomOverlay.style.display = 'none';
        }, 300); // Wait for animation to complete
        rulerVisible = false;
        saveSettings();
    }

    function updateRulerStyle() {
        // Apply transparency and theme
        ruler.style.backgroundColor = currentTheme;
        ruler.style.opacity = currentTransparency;

        // Adjust for focus style
        if (currentStyle === 'style1') {
            topOverlay.style.display = 'block';
            bottomOverlay.style.display = 'block';
            ruler.style.border = 'none';
        } else {
            topOverlay.style.display = 'none';
            bottomOverlay.style.display = 'none';
            ruler.style.border = `1px solid rgba(0, 0, 0, ${currentTransparency})`;
        }

        // Update overlay positions based on viewport
        if (selectedArea) {
            topOverlay.style.height = `${selectedArea.top}px`;
            bottomOverlay.style.top = `${selectedArea.bottom}px`;
            bottomOverlay.style.height = `${window.innerHeight - selectedArea.bottom}px`;
        }
    }

    function startAreaSelection() {
        const overlay = createSelectionOverlay();
        document.body.appendChild(overlay);

        let startX, startY;

        overlay.addEventListener('mousedown', (e) => {
            startX = e.clientX;
            startY = e.clientY;

            const selectionBox = document.createElement('div');
            selectionBox.style.position = 'fixed'; // Fixed to viewport
            selectionBox.style.border = '2px dashed #007AFF';
            selectionBox.style.backgroundColor = 'rgba(0, 122, 255, 0.1)';
            selectionBox.style.zIndex = '100001';
            document.body.appendChild(selectionBox);

            const onMouseMove = (moveEvent) => {
                const currentX = moveEvent.clientX;
                const currentY = moveEvent.clientY;

                const width = Math.abs(currentX - startX);
                const height = Math.abs(currentY - startY);

                selectionBox.style.left = `${Math.min(startX, currentX)}px`;
                selectionBox.style.top = `${Math.min(startY, currentY)}px`;
                selectionBox.style.width = `${width}px`;
                selectionBox.style.height = `${height}px`;
            };

            const onMouseUp = (mouseUpEvent) => {
                selectedArea = {
                    top: Math.min(startY, mouseUpEvent.clientY),
                    bottom: Math.max(startY, mouseUpEvent.clientY),
                    left: Math.min(startX, mouseUpEvent.clientX),
                    right: Math.max(startX, mouseUpEvent.clientX),
                    width: Math.abs(mouseUpEvent.clientX - startX),
                    height: Math.abs(mouseUpEvent.clientY - startY)
                };

                document.body.removeChild(overlay);
                document.body.removeChild(selectionBox);
                document.removeEventListener('mousemove', onMouseMove);
                document.removeEventListener('mouseup', onMouseUp);

                // Constrain the ruler within the selected area
                ruler.style.top = `${selectedArea.top}px`;
                ruler.style.left = `${selectedArea.left}px`;
                ruler.style.width = `${selectedArea.width}px`;
                ruler.style.height = `${selectedArea.height}px`;

                // Update overlays for style 1
                topOverlay.style.height = `${selectedArea.top}px`;
                bottomOverlay.style.top = `${selectedArea.bottom}px`;
                bottomOverlay.style.height = `${window.innerHeight - selectedArea.bottom}px`;

                showRuler(); // Automatically show ruler after selection
                saveSettings(); // Save the selected area
            };

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        });
    }

    function createSelectionOverlay() {
        const overlay = document.createElement('div');
        Object.assign(overlay.style, {
            position: 'fixed', // Fixed to viewport
            top: '0',
            left: '0',
            width: '100%',
            height: '100%',
            backgroundColor: 'rgba(0, 0, 0, 0.1)',
            zIndex: '100000',
            cursor: 'crosshair',
        });
        return overlay;
    }

    // Load saved settings on page load
    loadSettings();
})();

The (Web) Reading Ruler Published on 2024-10-01